feat(r3-a): prove the offline PMTiles map on macOS - #62
Merged
Conversation
Third and last capability the spike had to demonstrate. The probe runs on window start and records its verdict in the spike database, so the remaining targets can read it without driving a UI: ok, 87 source features, 7 painted, blockedRequests: [], transport: tauri-fs The fixture is built from the Natural Earth country geometry already vendored for the gazetteer — public domain, so redistributable, and nothing is fetched. build-pmtiles-fixture.mjs writes the PMTiles v3 container directly rather than shelling out to tippecanoe, so it rebuilds from a clean checkout with pnpm alone. 598 KiB, zooms 0-4. The style carries no glyphs or sprites, which would be hidden network dependencies. Two platform findings, both recorded in the evidence bundle: Tauri's app protocol does not support HTTP byte serving. It answers a Range request with the whole file and a 200, and the PMTiles reader refuses such a backend outright. The archive therefore ships as a bundle resource and is read through the fs plugin, which is closer to what a local-first build would do. That path needs its own ACL commands (open/seek/read/fstat); fs:allow-close does not exist and breaks the build. Reading it also has to be serialized: seek and read are two round trips over one shared cursor, and MapLibre requests tiles concurrently, so interleaved seeks hand each reader another range's bytes. The symptom surfaces far from the cause as a corrupt tile.
Both mobile targets run the offline map and SQLite probes for real: iOS 26.3.1 simulator ok, 48 source features, 3 painted, no network Android API 36.1 ok, 48 source features, 3 painted, no network The same TauriFileSource works on all three platforms despite different address shapes: macOS and iOS resolve the bundled archive to a filesystem path, Android to asset://localhost/... — the fs plugin opens and range-reads both without adaptation. Vault is not exercised on mobile: it depends on the folder picker, which needs interaction. The spec only asks for a capability check there, and that check is still outstanding — the evidence bundle says so rather than implying coverage. Also stop destroying the map once the probe has measured it. Tearing it down made it flash and vanish exactly when it succeeded, which is the opposite of what a proof screen should do. Generated mobile projects are not versioned: 855 MB of build scaffolding that "tauri ios init" and "tauri android init" reproduce. The evidence bundle carries the commands and the toolchain constraints found along the way — rustup rather than Homebrew rust, the foreground requirement for the iOS build, and Gradle's refusal of JDK 26.
Morialkar
force-pushed
the
feat/r3-a-pmtiles-probe
branch
from
August 4, 2026 01:49
58cf739 to
292fdea
Compare
Android running the probes on a Chromium webview is the closest available signal for WebView2, so the residual Windows risk is materially lower than when only WKWebView had been exercised. Record that, without upgrading an untested target to a proven one.
…raint The last outstanding combination in the target matrix. The check runs unattended so a smoke script can perform it, and reports both what app-scoped storage can do and whether a directory picker exists. Result on all three platforms: macOS app-scoped write PASS, directory picker supported iOS app-scoped write PASS, directory picker UNSUPPORTED Android app-scoped write PASS, directory picker UNSUPPORTED "Folder picker is not implemented on mobile" — so the desktop vault model, an arbitrary user-chosen folder such as an Obsidian directory, has no equivalent on iOS or Android with this stack. Mobile can write Markdown, but only inside storage that is private to the app and removed with it. That does not sink Tauri; it forces a decision about what a mobile vault is, which the evidence bundle now states with the three options rather than leaving it implied. The spec asks for the constraint to be recorded instead of faking desktop semantics. Writing into app data also needed fs:allow-appdata-write-recursive; without it the write is rejected as a forbidden path even with allow-write-text-file granted.
Declared on the evidence gathered: the three required capabilities pass on macOS, the iOS simulator and the Android emulator, across two webview engines. Tauri is adopted; the D8 fallback of Capacitor plus Electron is not taken. Two points ride along with the decision rather than being closed by it: Windows was never executed, and the mobile vault model still has to be chosen, since no directory picker exists on either mobile platform.
directoryPicker is assigned on every path through the try/catch, so seeding it with 'unknown' was never read. I had re-run tsc and Prettier after adding the capability probe but not ESLint, which is why CI caught it and I did not.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third and last capability R3-A had to demonstrate. macOS now passes all three; the target matrix is still open, so no Go/No-go is declared.
Result
The probe runs when the native window starts and records its verdict in the spike database, so the Windows, iOS and Android smoke runs can read it without driving a UI:
{ "ok": true, "sourceFeatures": 87, "renderedFeatures": 7, "blockedRequests": [], "transport": "tauri-fs" }87 features decoded from the bundled archive and 7 actually painted — the map draws, it does not merely load.
blockedRequests: []means no network resource was even asked for; the style deliberately carries no glyphs or sprites, which would be hidden network dependencies.The fixture
Built from the Natural Earth country geometry already vendored for the gazetteer — public domain, so redistributable, and nothing is downloaded. No OSM-derived data, no tile-server prefetching, as the spec requires.
packages/core/scripts/build-pmtiles-fixture.mjswrites the PMTiles v3 container directly instead of shelling out to tippecanoe, so the archive rebuilds from a clean checkout with pnpm alone. 598 KiB, zooms 0–4.It is validated two ways: a test asserts the v3 header, MVT tile type, declared layer and size; and every tile was parsed with
@mapbox/vector-tile(7/7 decoded with a populatedcountrieslayer) before any of this was blamed on Tauri.Two platform findings
Tauri's app protocol does not do HTTP byte serving. PMTiles reads an archive as byte ranges.
tauri://localhostanswersRange: bytes=0-126with200, noContent-Range, and all 612 390 bytes — and the reader refuses such a backend outright ("Check that your storage backend supports HTTP Byte Serving"). Serving the archive overtauri://does not work. It ships as a bundle resource and is read through the fs plugin instead, which is closer to what a local-first build would do anyway.That native path needs its own ACL grants —
fs:allow-open,fs:allow-seek,fs:allow-read,fs:allow-fstat— separate from thereadFile/writeTextFilehelpers. Notefs:allow-closedoes not exist and fails the build.Reads must be serialized. A seek followed by a read is two round trips over one shared cursor, and MapLibre requests tiles concurrently. Interleaved seeks hand each reader another range's bytes; the symptom appears far from the cause, as a corrupt tile ("Extra bytes past the end").
Both are written up in the evidence bundle so the mobile targets do not rediscover them.
Verification
probe_verdictsin the spike databasetsc -b, ESLint, Prettier cleanScope
Spike-only and disposable. The production map still uses OSM raster tiles over the network; this probe deliberately does not, because the spike has to show the map standing up with no connectivity.
Remaining before any decision: Windows, iOS simulator, Android emulator.